﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.Web.UI.HtmlControls;
using System.Collections;
using System.Reflection;
using System.Security.Permissions;
using System.Xml;
using System.Data;
using System.IO;
using System.Web.UI.Design;
using System.Drawing.Design;
using System.Globalization;
using System.ComponentModel.Design.Serialization;
using System.ComponentModel.Design;

namespace geoqualMenuBar
{
    [ValidationProperty("MenuBar"), DefaultProperty("MenuBar"), ToolboxBitmap(typeof(ResourceFinder), "geoqualMenuBar.GQMenuBar.bmp"),
    ParseChildren(true), PersistChildren(true),
    ToolboxData("<{0}:GQMenuBar runat=server></{0}:GQMenuBar>"),
    Designer(typeof(GQMenuBarControlDesigner))]
    public class GQMenuBar : WebControl, IPostBackEventHandler
    {
        #region Properties
        private const string PRE_ID = "btn";
        private const string gqNavMenuA = "gqNavMenuA";
        private const string gqNavMenuSPAN = "gqNavMenuspan";
        private const string gqNavMenuSPANVERT = "gqNavMenuspanVert";
        private const string SELECTED = "gqNavMenu_Selected";
        private const string UNSELECTED = "gqNavMenu_Unselected";
        private string gqNavMenuaclass = string.Empty;
        private string selectedclass = string.Empty;
        private string unselectedclass = string.Empty;
        private string gqNavMenuspanclass = string.Empty;
        private string gqNavMenuspanclassVert = string.Empty;
        private string thisUniqueID = string.Empty;

        private Color color = Color.White;
        private Color selectedColor = Color.White;
        private Color backgroundcolor = ColorTranslator.FromHtml("#316477");
        private Color backgroundcolor2 = ColorTranslator.FromHtml("#1D3C47");
        private Color selectedbackgroundcolor = ColorTranslator.FromHtml("#E91212");
        private Color selectedbackgroundcolor2 = ColorTranslator.FromHtml("#6C0909");
        private Unit newHeight = Unit.Parse("0");
        private Unit newWidth = Unit.Parse("0");
        private Unit minWidth = Unit.Parse("0");
        private Unit iconwidth = Unit.Parse("10");
        private Color hoverbackgroundcolor = ColorTranslator.FromHtml("#cccccc");
        private Color hoverbackgroundcolor2 = ColorTranslator.FromHtml("#FFAA7F");
        private string fontfamily = "'Segoe UI', Segoe, Tahoma, Helvetica, Arial, sans-serif";
        private int fsize = 12;
        private bool isVertical = false;

        private MenuBars menuBarList;
        public event CommandEventHandler Command;

        private string NAMESPACE = string.Empty;

        private enum BrowserName {
            Safari,
            Opera,
            Firefox,
            Standard
        }

        [Bindable(true), Category("Appearance"), DefaultValue(""), Localizable(true)]
        public string SelectedItem
        {
            get {
                String s = (String)ViewState["SelectedItem"];
                return ((s == null) ? string.Empty : s);
            }
            set {
                ViewState["SelectedItem"] = value;
            }
        }

        private string SelItem
        {
            get {
                return (string)ViewState["SelItem"];
            }
            set {
                ViewState["SelItem"] = value;
            }
        }

        [Bindable(false), Category("Data"), DefaultValue(""),
        Description("Full Path and filename to the Xml to be used as datasource")]
        public string NavMenuXML
        {
            set
            {
                if (!string.IsNullOrEmpty(value)) {
                    FileInfo fileinfo = new FileInfo(value);
                    if (fileinfo.Exists) {
                        XmlDocument doc = new XmlDocument();
                        doc.Load(value);
                        _dataSource = doc;
                    } else throw new Exception("Xml file does not exist.");
                }
            }
        }

        [Category("Behavior"), DefaultValue(null), Browsable(true),
        Description("The data source used to build up the menu. Must be serializable!"),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        Editor(typeof(GQMenuBarCollectionEditor), typeof(UITypeEditor)),
        PersistenceMode(PersistenceMode.InnerProperty)]
        public MenuBars MenuBarItems
        {
            get
            {
                if (menuBarList == null) {
                    menuBarList = new MenuBars();
                }
                return menuBarList;
            }
        }

        public void SetMenuBar(MenuBars value)
        {
            menuBarList = value;
        }

        [Bindable(false), Category("Appearance"), DefaultValue("Color.White"), Localizable(true)]
        public Color Color
        {
            get { return color; }
            set { color = value; }
        }

        [Bindable(false), Category("Appearance"), DefaultValue("Color.White"), Localizable(true)]
        public Color SelectedColor
        {
            get { return selectedColor; }
            set { selectedColor = value; }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public override Color BackColor
        {
            get { return backgroundcolor; }
            set { backgroundcolor = value; }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public Color BackColor2
        {
            get { return backgroundcolor2; }
            set { backgroundcolor2 = value; }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public Color SelectedBackgroundColor
        {
            get { return selectedbackgroundcolor; }
            set { selectedbackgroundcolor = value; }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public Color SelectedBackgroundColor2
        {
            get { return selectedbackgroundcolor2; }
            set { selectedbackgroundcolor2 = value; }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public Color HoverBackgroundColor
        {
            get { return hoverbackgroundcolor; }
            set { hoverbackgroundcolor = value; }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public Color HoverBackgroundColor2
        {
            get { return hoverbackgroundcolor2; }
            set { hoverbackgroundcolor2 = value; }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public override Unit Height
        {
            get { return newHeight; }
            set {
                if (value.Value < 0)
                    throw new Exception("Can't be negative.");
                newHeight = value; 
            }
        }

        [Bindable(false), Category("Appearance"), DefaultValue("false"), Localizable(true)]
        public bool Vertical {
            get { return isVertical; }
            set {
                isVertical = value;
            }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public override Unit Width
        {
            get { return newWidth; }
            set {
                if (value.Value < 0)
                    throw new Exception("Can't be negative.");
                newWidth = value; 
            }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public Unit MinWidth
        {
            get { return minWidth; }
            set {
                if (value.Value < 0)
                    throw new Exception("Can't be negative.");
                minWidth = value; 
            }
        }

        [Bindable(false), Category("Appearance"), Localizable(true)]
        public string Fontfamily
        {
            get { return fontfamily; }
            set
            {
                fontfamily = value;
            }
        }

        [Bindable(false), Category("Appearance"), Localizable(true), DefaultValue(12)]
        public int FontSize
        {
            get { return fsize; }
            set
            {
                fsize = value;
            }
        }

        [Bindable(false), Category("Appearance"), Localizable(true), DefaultValue("10")]
        public Unit IconWidth {
            get { return iconwidth; }
            set {
                iconwidth = value;
            }
        }

        internal Color BaseBackColor {
            get { return base.BackColor; }
            set { base.BackColor = value; }
        }

        #endregion

        /// <summary>
		/// Basic constuctor
		/// </summary>
		public GQMenuBar() 
        {
        }

        private IEnumerable _dataSource;
        public override void DataBind()
        {
            this.Page.EnableViewState = true;
            base.OnDataBinding(EventArgs.Empty);
        }

        protected virtual void OnCommand(CommandEventArgs e)
        {
            if (Command != null)
                Command(this, e);
        }

        public void RaisePostBackEvent(string eventArgument)
        {
            SelItem = eventArgument.Substring(PRE_ID.Length);
            OnCommand(new CommandEventArgs(eventArgument, "Clicked"));
        }

        protected override void CreateChildControls()
        {
            thisUniqueID = this.UniqueID.Replace("$", string.Empty);
            Type myType = typeof(GQMenuBar);
            NAMESPACE = myType.Namespace;

            gqNavMenuaclass = gqNavMenuA + thisUniqueID;
            selectedclass = SELECTED + thisUniqueID;
            unselectedclass = UNSELECTED + thisUniqueID;
            gqNavMenuspanclass = gqNavMenuSPAN + thisUniqueID;
            gqNavMenuspanclassVert = gqNavMenuSPANVERT + thisUniqueID;

            outerUl = new HtmlGenericControl(HtmlTextWriterTag.Ul.ToString());
            outerUl.ID = string.Concat("gqNavMenu", thisUniqueID);
            string addClass = "gqNavMenu";
            if (isVertical)
                addClass += " " + gqNavMenuspanclassVert;
            outerUl.Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), addClass);

            if (menuBarList != null) {
                _dataSource = (IEnumerable)menuBarList;
            }
            if (_dataSource != null) {
                XmlDocument xmlDoc = null;
                if (_dataSource is XmlDocument) {
                    xmlDoc = (XmlDocument)_dataSource;
                } else if (_dataSource is MenuBars) {
                    xmlDoc = createXmlDoc((MenuBars)_dataSource);
                }
                if (xmlDoc != null) CreateControls(xmlDoc);
            }
        }

        private XmlDocument createXmlDoc(MenuBars menu)
        {
            try {
                return Utils.CreateXmlDoc(menu);
            } catch (Exception) {
                throw new Exception("Invalid MenuBar doc!");
            }
        }

        private XmlDocument analyzeDataSource(IEnumerable datasource)
        {
            XmlDocument xmlDoc = new XmlDocument();
            try {
                xmlDoc.LoadXml(Utils.SerializeToStream(datasource));
            } catch (Exception e) {
                throw new Exception("Invalid datasource! Object must be serializable and of known (public) type!");
            }
            return xmlDoc;
        }

        private HtmlGenericControl outerUl;

        private void CreateControls(XmlDocument xmlDoc)
        {
            XmlElement root = xmlDoc.DocumentElement;
            if (root.HasChildNodes) {
                Controls.Clear();

                //loop through nodes
                CreateControls(root.ChildNodes);
                this.Controls.Add(outerUl);
                ChildControlsCreated = true;
            }
        }

        private void CreateControls(XmlNodeList nodes)
        {
            foreach (XmlNode node in nodes) {
                HtmlGenericControl menuItem = getInnerHtml(null, node);
                outerUl.Controls.Add(menuItem);
                if (Utils.HasSubMenu(node)) {
                    if (menuItem.Attributes.Count > 0 && menuItem.Attributes[HtmlTextWriterAttribute.Onclick.ToString()] != null)
                        menuItem.Attributes.Remove(HtmlTextWriterAttribute.Onclick.ToString());
                    loopThroughSublevels(node, menuItem);
                }
            }
        }

        private void loopThroughSublevels(XmlNode parentnode, HtmlGenericControl parentMenuitem)
        {
            HtmlGenericControl outerUl = new HtmlGenericControl(HtmlTextWriterTag.Ul.ToString());
            parentMenuitem.Controls.Add(outerUl);
            if (parentnode.HasChildNodes) {
                foreach (XmlNode node in parentnode.ChildNodes) {
                    if (node.NodeType != XmlNodeType.Text) {
                        HtmlGenericControl menuItem = null;
                        if (Utils.IsSubMenuNode(node, parentnode)) {
                            menuItem = getInnerHtml(parentMenuitem, node);
                            outerUl.Controls.Add(menuItem);
                            if (Utils.HasSubMenu(node)) {
                                if (menuItem.Attributes.Count > 0 && menuItem.Attributes[HtmlTextWriterAttribute.Onclick.ToString()] != null)
                                    menuItem.Attributes.Remove(HtmlTextWriterAttribute.Onclick.ToString());

                                loopThroughSublevels(node, menuItem);
                            }
                        }
                    }
                }
            }
        }

        private HtmlGenericControl getInnerHtml(HtmlGenericControl parent, XmlNode node)
        {
            bool hasIcon = false;
            int iwidth = 0;
            string imageurl = string.Empty;

            HtmlGenericControl innerli = new HtmlGenericControl(HtmlTextWriterTag.Li.ToString());
            HtmlGenericControl ahref = new HtmlGenericControl(HtmlTextWriterTag.A.ToString());
            if (node.Attributes.Count > 0) {
                foreach (XmlAttribute attr in node.Attributes) {
                    if (attr.Name.ToLower() == "tooltip") {
                        ahref.Attributes.Add(HtmlTextWriterAttribute.Title.ToString(), attr.Value.ToString());
                    }
                    if (attr.Name.ToLower() == "icon" || attr.Name.ToLower() == "iconurl") {
                        hasIcon = true;
                        imageurl = attr.Value.ToString();
                    }
                }
            }

            AddWidth(innerli, node, iwidth);

            ahref.Attributes.Add(HtmlTextWriterAttribute.Href.ToString(), "#");
            AddWidth(ahref, node, iwidth);

            HtmlGenericControl icondiv = new HtmlGenericControl(HtmlTextWriterTag.Div.ToString());
            if (hasIcon) {
                iwidth = (int)iconwidth.Value;
                icondiv.Attributes.Add("style", "width: 100%;");

                HtmlGenericControl iconspan = new HtmlGenericControl(HtmlTextWriterTag.Span.ToString());
                iconspan.Attributes.Add("style", string.Format("width: {0}px;", iconwidth.ToString()));
                iconspan.Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), gqNavMenuspanclass);

                System.Web.UI.WebControls.Image img = new System.Web.UI.WebControls.Image();
                img.ImageUrl = imageurl;
                img.Width = iconwidth;
                img.ImageAlign = ImageAlign.Middle;
                iconspan.Controls.Add(img);

                icondiv.Controls.Add(iconspan);
            }
            HtmlGenericControl span = new HtmlGenericControl(HtmlTextWriterTag.Span.ToString());
            span.Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), gqNavMenuspanclass);
            AddWidth(span, node, iwidth);
            icondiv.Controls.Add(span);

            span.InnerText = Utils.GetInnerValue(node);

            if (hasIcon)
                ahref.Controls.Add(icondiv);
            else
                ahref.Controls.Add(span);

            innerli.ID = string.Concat(PRE_ID, node.Name);

            string sitem = SelItem;
            if (string.IsNullOrEmpty(sitem)) sitem = SelectedItem;
            if (sitem != string.Empty && innerli.ID != string.Concat(PRE_ID, sitem)) {
                ahref.Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), string.Concat(gqNavMenuaclass, " ", gqNavMenuspanclass, " ", unselectedclass)); 
                innerli.Attributes.Add(HtmlTextWriterAttribute.Onclick.ToString(), Page.ClientScript.GetPostBackEventReference(this, innerli.ID));
            } else {
                ahref.Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), string.Concat(gqNavMenuaclass, " ", gqNavMenuspanclass, " ", selectedclass));
                innerli.Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), selectedclass);
                if (parent != null)
                    setParentSelected(parent);
                if (string.IsNullOrEmpty(sitem))
                    sitem = innerli.ID.Substring(PRE_ID.Length);
                SelItem = sitem;
                SelectedItem = sitem;
            }
            innerli.Controls.Add(ahref);
            return innerli;
        }

        private void AddWidth(HtmlGenericControl gencontrol, XmlNode node, int iconwidth) {
            double w = 0.0;
            if (node.Attributes["width"] != null) {
                double.TryParse(node.Attributes["width"].Value.ToString(), out w);
            } else {
                w = newWidth.Value;
            }
            w -= iconwidth;
            if (w > 0) {
                gencontrol.Attributes.Add(HtmlTextWriterAttribute.Style.ToString(), string.Concat("width: ", ((int)w).ToString(), "px !important;"));
            } else {
                gencontrol.Attributes.Add(HtmlTextWriterAttribute.Style.ToString(), "width: auto !important;");
            }
        }

        private void setParentSelected(HtmlGenericControl parent)
        {
            if (parent != null) {
                //the outer tag should always keep the gqNavMenu class, so never overwrite it.
                if (!parent.Equals(outerUl)) {
                    parent.Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), string.Concat(gqNavMenuaclass, " ", selectedclass));
                    ((HtmlGenericControl)parent.Controls[0]).Attributes.Add(HtmlTextWriterAttribute.Class.ToString(), string.Concat(gqNavMenuaclass, " ", selectedclass));
                    if (parent.Parent != null && parent.Parent is HtmlGenericControl)
                        setParentSelected((HtmlGenericControl)parent.Parent);
                }
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            string location = Page.ClientScript.GetWebResourceUrl(typeof(ResourcesClass), string.Concat(NAMESPACE, ".MenuBarCSS.css"));
            AddMenuBarCSSToPage(location);

            this.Page.ClientScript.GetPostBackEventReference(this, string.Empty);

            string menubardynamicscss = string.Concat(NAMESPACE, ".MenuBarDynamicCSS.css");
            location = Page.ClientScript.GetWebResourceUrl(typeof(ResourcesClass), menubardynamicscss);

            string ls_css = string.Empty;

            Stream l_css_res = Assembly.GetExecutingAssembly().GetManifestResourceStream(menubardynamicscss);
            using (StreamReader l_stream = new StreamReader(l_css_res)) 
            {
                ls_css = l_stream.ReadToEnd();

                //make sure there is no confusion in case there are more MenuBars on the page
                string addunique = string.Empty;
                if (this.UniqueID.Contains("$")) {
                    addunique = this.UniqueID.Substring(0, this.UniqueID.IndexOf("$"));
                }
                //now change the dynamic params
                ls_css = ls_css.Replace("$id$", thisUniqueID);

                ls_css = ls_css.Replace("$selectedcolor$", ColorTranslator.ToHtml(selectedColor));
                ls_css = ls_css.Replace("$color$", ColorTranslator.ToHtml(color));

                ls_css = ls_css.Replace("$backgroundcolor$", ColorTranslator.ToHtml(backgroundcolor));
                ls_css = ls_css.Replace("$backgroundcolor2$", ColorTranslator.ToHtml(backgroundcolor2));

                ls_css = ls_css.Replace("$selectedbackgroundcolor$", ColorTranslator.ToHtml(selectedbackgroundcolor));
                ls_css = ls_css.Replace("$selectedbackgroundcolor2$", ColorTranslator.ToHtml(selectedbackgroundcolor2));

                ls_css = ls_css.Replace("$hoverbackgroundcolor$", ColorTranslator.ToHtml(hoverbackgroundcolor));
                ls_css = ls_css.Replace("$hoverbackgroundcolor2$", ColorTranslator.ToHtml(hoverbackgroundcolor2));

                if (newHeight.Value == 0) {
                    ls_css = ls_css.Replace("$height$", "auto");
                } else {
                    ls_css = ls_css.Replace("$height$", string.Concat(newHeight.Value.ToString(), "px"));
                }
                if (newWidth.Value == 0) {
                    ls_css = ls_css.Replace("$width$", "auto");
                } else {
                    ls_css = ls_css.Replace("$width$", string.Concat(newWidth.Value.ToString(), "px"));
                }
                if (minWidth.Value == 0) {
                    ls_css = ls_css.Replace("$minwidth$", "auto");
                } else {
                    ls_css = ls_css.Replace("$minwidth$", string.Concat(minWidth.Value.ToString(), "px"));
                }
                ls_css = ls_css.Replace("$fontfamily$", this.fontfamily);
                ls_css = ls_css.Replace("$fontsize$", this.fsize.ToString());
                HttpBrowserCapabilities browser = Page.Request.Browser;

                BrowserName browsername = BrowserName.Standard;
                if (Enum.IsDefined(typeof(BrowserName), browser.Browser))
                    browsername = (BrowserName)Enum.Parse(typeof(BrowserName), browser.Browser);

                string lineargradient = "linear-gradient(";
                double browserversion = 0;
                double.TryParse(browser.Version, out browserversion);
                switch (browsername) {
                    case BrowserName.Firefox:
                        if (browserversion >= 3.6 && browserversion < 15.0) {
                            ls_css = ls_css.Replace(lineargradient, "-moz-linear-gradient(");
                        }
                        break;
                    case BrowserName.Opera:
                        if (browserversion >= 11.1 && browserversion < 12.0) {
                            ls_css = ls_css.Replace(lineargradient, "-o-linear-gradient(");
                        }
                        break;
                    case BrowserName.Safari:
                        if (browserversion >= 5.1 && browserversion < 6.0) {
                            ls_css = ls_css.Replace(lineargradient, "-webkit-linear-gradient(");
                        }
                        break;
                }
            }
            string formattemp = @"<style>{0}</style>";

            LiteralControl dynamiccss = new LiteralControl(string.Format(formattemp, ls_css));
            HtmlHead htmlHeader = (HtmlHead)Page.Header;
            htmlHeader.Controls.Add(dynamiccss);

            base.OnPreRender(e);
        }

        private void AddMenuBarCSSToPage(string location, string linkUniqueID = "linkGQMenuBarCSS", string css = ".MenuBarCSS.css") {
            string tempLink = "<link id='{0}' rel='stylesheet' text='text/css' href='{1}' />";
            try {
                Control ctrl = Page.FindControl(linkUniqueID);
                if (ctrl == null) {
                    LiteralControl include = new LiteralControl(string.Format(tempLink, linkUniqueID, location));
                    HtmlHead htmlHead = (System.Web.UI.HtmlControls.HtmlHead)Page.Header;
                    htmlHead.Controls.Add(include);
                }
            } catch {
                //other menubar already attached to the page, so no need to include the css again
            }
        }

        protected override void RenderContents(HtmlTextWriter writer) {
            this.EnsureChildControls();
            base.RenderContents(writer);
        }

        protected override void RenderChildren(HtmlTextWriter writer)
        {
            base.RenderChildren(writer);
        }
    }
}
